package com.imooc.coupon.executor.impl;

import com.alibaba.fastjson.JSON;
import com.imooc.coupon.constant.CouponCategory;
import com.imooc.coupon.support.vo.GoodsInfo;
import com.imooc.coupon.support.vo.SettlementInfo;
import com.imooc.coupon.constant.RuleFlag;
import com.imooc.coupon.executor.AbstractRuleExecutor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 满减 & 折扣优惠券结算规则执行器
 *
 * Created By TINY
 * Create Time 2020/2/12
 **/
@Slf4j
@Component
public class FullReduceDiscExecutor extends AbstractRuleExecutor {

    /**
     * 实现满减 + 折扣优惠券的规则校验（校验优惠券是否支持商品类型）；
     * 如果要使用组合优惠券，用户传递的商品类型必须都包含在模板的商品类型内，
     * 即（用户商品类型）不能多于模板中定义的商品类型
     *
     * @param settlementInfo {@link SettlementInfo} 结算信息对象
     * @return 是否支持商品类型
     */
    @Override
    @SuppressWarnings("all")
    protected boolean isGoodsTypeSupported(SettlementInfo settlementInfo) {

        log.info("检查满减 & 折扣优惠券是否支持当前商品");
        // 获取传入的所有商品类型
        List<Integer> goodsType = settlementInfo.getGoodsInfos().stream()
                .map(GoodsInfo::getType).collect(Collectors.toList());

        // 优惠券模板中支持的商品类型
        List<Integer> goodsTypeFromTemplate = new ArrayList<>();

        settlementInfo.getCouponAndTemplateInfos().forEach(
                tc -> goodsTypeFromTemplate.addAll(JSON.parseObject(
                        tc.getTemplate().getRule().getUsageScope().getGoodsType(),
                        List.class
                ))
        );

        // 如果要使用组合优惠券，用户传递的商品类型必须都包含在模板的商品类型内
        // 即不能多于模板中定义的商品类型
        return CollectionUtils.isEmpty(CollectionUtils.subtract(
                goodsType, goodsTypeFromTemplate));
    }

    /**
     * 优惠券结算规则类型标记
     *
     * @return {@link RuleFlag} 优惠券结算规则标记
     */
    @Override
    public RuleFlag supports() {
        return RuleFlag.FULL_REDUCE_DISC;
    }

    /**
     * 核心的优惠券规则计算逻辑实现，实现‘满减’ + ‘折扣’优惠券的规则计算
     *
     * @param settlementInfo {@link SettlementInfo} 结算信息对象
     * @return {@link SettlementInfo} 进行结算处理后的结算信息对象
     */
    @Override
    protected SettlementInfo cProcess(SettlementInfo settlementInfo) {
        // 满减优惠券
        SettlementInfo.CouponAndTemplateInfo fullReduction = null;

        // 折扣优惠券
        SettlementInfo.CouponAndTemplateInfo discount = null;

        for (SettlementInfo.CouponAndTemplateInfo ct :
                settlementInfo.getCouponAndTemplateInfos()) {
            if (CouponCategory.of(ct.getTemplate().getCategory())
                    == CouponCategory.FULL_REDUCTION) {
                fullReduction = ct;
            } else { discount = ct; }
        }

        // 断言：满减 & 折扣优惠券都不能为空
        assert null != fullReduction;
        assert null != discount;

        // 得到原始商品总价
        final double goodsOriginalCost = settlementInfo.getCost();

        // 当前的两张优惠券如果不能共用，清空优惠券列表，返回商品原始总价
        if (!isCanUseTogether(fullReduction, discount)) {
            log.info("满减 & 折扣优惠券不能一起使用");
            // 设置总价为商品原始总价
            settlementInfo.setCost(goodsOriginalCost);
            // 清空优惠券列表
            settlementInfo.setCouponAndTemplateInfos(Collections.emptyList());

            return settlementInfo;
        }

        // 已经使用的优惠券（符合条件之后优惠券就可以被使用）
        List<SettlementInfo.CouponAndTemplateInfo> ctInfos = new ArrayList<>();
        // 满减基准值
        double fullReductionBase = (double) fullReduction.getTemplate()
                .getRule().getDiscount().getBase();

        // 满减折扣额度
        double fullReductionQuota = (double) discount.getTemplate()
                .getRule().getDiscount().getQuota();

        // 最终价格
        double targetCost = goodsOriginalCost;

        // 先计算满减，因为有门槛，而折扣无门槛
        if (targetCost > fullReductionBase) {
            targetCost -= fullReductionQuota; // 满减计算
            ctInfos.add(fullReduction);
        }

        // 折扣计算
        double discountQuota = (double) discount.getTemplate()
                .getRule().getDiscount().getQuota();
        targetCost *= discountQuota * 1.0 / 100;
        ctInfos.add(discount);

        // 实际执行结算的优惠券
        settlementInfo.setCouponAndTemplateInfos(ctInfos);
        settlementInfo.setCost(retain2Decimals(
                targetCost > minCost() ? targetCost : minCost()
        ));

        return settlementInfo;
    }

    /* 根据 weight 判断两张优惠券能否一起使用 */
    @SuppressWarnings("all")
    private boolean isCanUseTogether(
            SettlementInfo.CouponAndTemplateInfo fullReduction,
            SettlementInfo.CouponAndTemplateInfo discount) {

        // 满减优惠券 key
        String fullReductionKey = fullReduction.getTemplate().getKey()
                + String.format("%04d", fullReduction.getTemplate().getId());

        // 折扣优惠券 key
        String discountKey = discount.getTemplate().getKey()
                + String.format("%04d", discount.getTemplate().getId());

        // 能与满减优惠券一起使用的优惠券 key
        List<String> canUseWithFullReduction = new ArrayList<>();
        canUseWithFullReduction.add(fullReductionKey);
        canUseWithFullReduction.addAll(JSON.parseObject(
                fullReduction.getTemplate().getRule().getWeight(), List.class
        ));

        // 能与折扣优惠券一起使用的优惠券 key
        List<String> canUseWithDiscount = new ArrayList<>();
        canUseWithDiscount.add(discountKey);
        canUseWithDiscount.addAll(JSON.parseObject(
                discount.getTemplate().getRule().getWeight(), List.class
        ));

        // 满减优惠券 key 与 折扣优惠券的 key 是 canUseWithFullReduction 或
        // canUseWithDiscount 的子集就表示两张优惠券可以一起使用
        return CollectionUtils.isSubCollection(Arrays.asList(
                fullReductionKey, discountKey), canUseWithFullReduction)
                || CollectionUtils.isSubCollection(Arrays.asList(
                        fullReductionKey, discountKey), canUseWithDiscount);
    }
}
